/*
 *    Webical - http://www.webical.org
 *    Copyright (C) 2007 Func. Internet Integration
 *
 *    This file is part of Webical.
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.webical.web.component.event;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import net.fortuna.ical4j.util.Calendars;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.wicket.extensions.markup.html.form.DateTextField;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.RequiredTextField;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.StringResourceModel;
import org.webical.Calendar;
import org.webical.Event;
import org.webical.User;
import org.webical.ical.Recurrence;
import org.webical.ical.RecurrenceUtil;
import org.webical.manager.WebicalException;
import org.webical.web.app.WebicalWebAplicationException;
import org.webical.web.component.calendar.RecurrenceFrequentyStringResourceModel;

/**
 * The form used for adding and editing events. This class has two abstract methods
 * that have to be implemented by the panel to handle the persist and discard actions
 * generated by the form.
 *
 * @author Mattijs Hoitink
 *
 */
public abstract class EventForm extends Form {

	private static final long serialVersionUID = 0L;
	private static Log log = LogFactory.getLog(EventAddEditPanel.class);

	// markup ID's
	private static final String HEADING_LABEL_MARKUP_ID 				= "headingLabel";
	private static final String FORM_FEEDBACK_MARKUP_ID 				= "formFeedback";

	private static final String EVENT_SUMMARY_TEXTFIELD_MARKUP_ID 		= "summary";
	private static final String EVENT_START_TEXTFIELD_MARKUP_ID 		= "dtStart";
	private static final String EVENT_END_TEXTFIELD_MARKUP_ID 			= "dtEnd";
	private static final String EVENT_LOCATION_TEXTFIELD_MARKUP_ID 		= "location";
	private static final String EVENT_CALENDAT_DROPDOWN_MARKUP_ID 		= "calendar";
	private static final String EVENT_DESCRIPTION_TEXTAREA_MARKUP_ID 	= "description";
	private static final String REPEAT_INTERVAL_TEXTFIELD_MARKUP_ID 	= "interval";
	private static final String REPEAT_COUNT_TEXTFIELD_MARKUP_ID 		= "count";
	private static final String REPEAT_UNTIL_TEXTFIELD_MARKUP_ID 		= "until";

	private static final String ALLDAY_BUTTON_MARKUP_ID 				= "alldayButton";
	private static final String DISCARD_LINK_MARKUP_ID 					= "discardLink";
	private static final String EVENT_SUBMIT_BUTTON_MARKUP_ID 			= "eventSubmitButton";

	// form elements
	private Label headingLabel;
	private FeedbackPanel formFeedback;
	private RequiredTextField summaryTextField;
	private DateTextField startDateTextField, endDateTextField;
	private TextField locationTextField, repeatIntervalTextField, repeatCountTextField, repeatUntilTextField;
	private DropDownChoice calendarDropDownChoice, repeatFrequencyDropDownChoice;
	private TextArea descriptionTextArea;
	private List<RecurrenceFrequentyStringResourceModel> frequencyModels;
	private Link discardLink;
	private Button allDayButton;
	private Button submitButton;

	// PropertyModel fields
	private RecurrenceFrequentyStringResourceModel frequency = null;
	private Integer interval = null;
	private Integer count = null;
	private Date until = null;

	private List<Calendar> availableCalendars;
	private Event formEvent;
	@SuppressWarnings("unused")
	private Event oldEvent;
	private IModel eventModel;
	// TOD mattijs: check what this variable is used for
	private GregorianCalendar calendar;

	private boolean editForm;
	private SimpleDateFormat dateFormat;

	/**
	 * Constructor
	 * @param id the markup id
	 * @param availableCalendars the {@link Calendars} available to the {@link User}
	 * @param editEvent the {@link Event} to be edited, null for a new Event
	 * @param gCalendar
	 * @param excludeFromRange if the {@link Event} should be excluded from the recurrence range
	 * @param startTime
	 *
	 * TODO check what the variable starttime is used for as it is unused right now
	 */
	public EventForm(String id, List<Calendar> availableCalendars, Event editEvent, GregorianCalendar gCalendar, boolean excludeFromRange, String startTime) {
		super(id);
		this.availableCalendars = availableCalendars;
		this.calendar = gCalendar;
		this.formEvent = editEvent;

		if(excludeFromRange){
			try {
				this.oldEvent = (Event) BeanUtils.cloneBean(editEvent);
			} catch (Exception e) {
				log.error("could not clone event: " + editEvent.getUid(), e);
				throw new WebicalWebAplicationException(e);
			}
		}

		/** Check if we are editing an event **/
		if(editEvent != null) {
			this.eventModel = new CompoundPropertyModel(editEvent);
			this.editForm = true;

			if(editEvent.getrRule().size() != 0 && !excludeFromRange){

				Recurrence recurrence = null;
				try {
					recurrence = RecurrenceUtil.getRecurrenceFromRecurrenceRuleSet(editEvent.getrRule());
				} catch (WebicalException e) {
					throw new WebicalWebAplicationException(e);
				}
				if(recurrence != null && recurrence.getFrequency() > 0 && recurrence.getInterval() != -1){

					int frequency = recurrence.getFrequency();

					RecurrenceFrequentyStringResourceModel myStringResourceModel = RecurrenceFrequentyStringResourceModel.getStringResourceModel(frequency, frequencyModels);
					if(myStringResourceModel != null){
						this.setFrequency(myStringResourceModel);
					}

					this.setInterval(new Integer(recurrence.getInterval()));

					if(recurrence.getCount() != -1){
						this.setCount(new Integer(recurrence.getCount()));
					}
					if(recurrence.getEndDay() != null){
						this.setUntil(recurrence.getEndDay());
					}
				}
			}
		} else {
			this.eventModel = new CompoundPropertyModel(this.createEmptyEvent());
			this.editForm = false;
		}
		setModel(eventModel);

		createFormElements();
		if(this.editForm) {
			alterFormForEditing();
		}
		addFormElements();
	}

	/**
	 * Method fired when onSubmit is finished. Must be implemented by the panel for persisting the event
	 * @param persistEvent the Event that has to be persisted
	 */
	public abstract void persistEvent(Event storeEvent);

	/**
	 * Method fired when discard button is pressed.
	 * The panel implements this method so it can cancel the form via the FormListener
	 */
	public abstract void onDiscard();

	/* (non-Javadoc)
	 * @see org.apache.wicket.markup.html.form.Form#onSubmit()
	 */
	@Override
	public void onSubmit() {
		// Do some checks to see if the dates are filled in correct
		if(!formEvent.isAllDay() && !formEvent.getDtStart().before(formEvent.getDtEnd())) {
			error(new StringResourceModel("Exception.endbeforestart", this, null).toString());
		} else if(!formEvent.isAllDay() && formEvent.getDtEnd().equals(formEvent.getDtStart())){
			error(new StringResourceModel("Exception.endbeforestart", this, null).toString());
		} else if(findSubmittingButton().equals(submitButton)) {
			persistEvent(formEvent);
		}
	}

	/**
	 * Create the form elements
	 */
	private void createFormElements() {
		// Heading
		headingLabel = new Label(HEADING_LABEL_MARKUP_ID, "Add Event");
		// Submit Button
		submitButton = new Button(EVENT_SUBMIT_BUTTON_MARKUP_ID, new Model("Add"));
		// Feedback Panel
		formFeedback = new FeedbackPanel(FORM_FEEDBACK_MARKUP_ID);
		//Title
		summaryTextField = new RequiredTextField(EVENT_SUMMARY_TEXTFIELD_MARKUP_ID);

		// Date fields
		dateFormat = (SimpleDateFormat) SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale());
		startDateTextField = (DateTextField) new DateTextField(EVENT_START_TEXTFIELD_MARKUP_ID, dateFormat.toLocalizedPattern()).setRequired(true);
		endDateTextField = (DateTextField) new DateTextField(EVENT_END_TEXTFIELD_MARKUP_ID, dateFormat.toLocalizedPattern()).setRequired(true);

		// All Day Button
		allDayButton = new Button(ALLDAY_BUTTON_MARKUP_ID, new Model("All Day")) {
			private static final long serialVersionUID = 1L;

			/* (non-Javadoc)
			 * @see org.apache.wicket.markup.html.form.Button#onSubmit()
			 */
			@Override
			public void onSubmit() {
				if(formEvent.isAllDay()) {
					setNormalDayEventFields();
				}
				else {
					setAllDayEventFields();
				}
			}

		};
		allDayButton.setDefaultFormProcessing(false);

		// Location
		locationTextField = new TextField(EVENT_LOCATION_TEXTFIELD_MARKUP_ID);
		// Calendar
		calendarDropDownChoice = (DropDownChoice) new DropDownChoice(EVENT_CALENDAT_DROPDOWN_MARKUP_ID, this.availableCalendars).setRequired(true);
		if(availableCalendars.size() > 0) {
			formEvent.setCalendar(availableCalendars.get(0));
		}
		// Description
		descriptionTextArea = new TextArea(EVENT_DESCRIPTION_TEXTAREA_MARKUP_ID);

		// Frequency
		frequencyModels = RecurrenceFrequentyStringResourceModel.createListOfStringResourceModel(EventForm.this);
		repeatFrequencyDropDownChoice = new DropDownChoice("frequency", new PropertyModel(this, "frequency"), frequencyModels);

		// Interval
		repeatIntervalTextField = new TextField(REPEAT_INTERVAL_TEXTFIELD_MARKUP_ID, new PropertyModel(this, "interval"), Integer.class);
		// Count
		repeatCountTextField = new TextField(REPEAT_COUNT_TEXTFIELD_MARKUP_ID, new PropertyModel(this, "count"), Integer.class);
		// Until
		repeatUntilTextField = new TextField(REPEAT_UNTIL_TEXTFIELD_MARKUP_ID, new PropertyModel(this, "until"), Date.class);

		// Discard Link
		discardLink =  new Link(DISCARD_LINK_MARKUP_ID) {
			private static final long serialVersionUID = 1L;

			/**
			 * passes the cancel call to the panel
			 * @see wicket.markup.html.link.Link#onClick()
			 */
			@Override
			public void onClick() {
				onDiscard();
			}

		};
	}

	/**
	 * Alters the form elements for rendering an edit form
	 */
	private void alterFormForEditing() {
		// Change the form header
		headingLabel.setModel(new Model("Edit event"));
		// Change the submit button text
		submitButton.setModel(new Model("Edit"));
		// set the correct calendar for the drop down choice
		calendarDropDownChoice.setModel(new PropertyModel(formEvent, "calendar"));
		if(this.formEvent.isAllDay()) {
			setAllDayEventFields();
		}
	}

	/**
	 * Add the form elements to the form
	 */
	private void addFormElements() {
		add(headingLabel);
		add(formFeedback);
		add(summaryTextField);
		add(startDateTextField);
		add(endDateTextField);
		add(allDayButton);
		add(calendarDropDownChoice);
		add(locationTextField);
		add(descriptionTextArea);
		add(repeatFrequencyDropDownChoice);
		add(repeatIntervalTextField);
		add(repeatCountTextField);
		add(repeatUntilTextField);
		add(discardLink);
		add(submitButton);
	}

	/**
	 * Create an empty Event and set the start and end times
	 * @param calendar
	 * @return Event
	 */
	private Event createEmptyEvent() {
		this.formEvent = new Event();

		java.util.Calendar dateCalendar = GregorianCalendar.getInstance();

		//Fill the start and end times with proper values
		this.formEvent.setDtStart(dateCalendar.getTime());
		dateCalendar.set(GregorianCalendar.HOUR_OF_DAY, dateCalendar.get(GregorianCalendar.HOUR_OF_DAY) + 1);
		this.formEvent.setDtEnd(dateCalendar.getTime());

		return this.formEvent;
	}

	/**
	 * Update the form and event for an all day event
	 */
	public void setAllDayEventFields() {
		formEvent.setAllDay();

		// Set a new date format
		dateFormat = (SimpleDateFormat) SimpleDateFormat.getDateInstance(DateFormat.SHORT, getLocale());

		// Replace the DateTextField
		replaceFormDateComponents(true);
	}

	/**
	 * Update the form and event for a non all day event
	 */
	public void setNormalDayEventFields() {
		formEvent.resetAllDay();

		// Set a new date format
		dateFormat = (SimpleDateFormat) SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale());

		// Replace the DateTextFields
		replaceFormDateComponents(false);
	}

	/**
	 * Replace the DateTextFields on the form, and update the allDay button
	 * @param allDay wheter the form is used for an all day event
	 */
	public void replaceFormDateComponents(boolean allDay) {
		startDateTextField = (DateTextField) new DateTextField(EVENT_START_TEXTFIELD_MARKUP_ID, dateFormat.toLocalizedPattern()).setRequired(true);
		endDateTextField = (DateTextField) new DateTextField(EVENT_END_TEXTFIELD_MARKUP_ID, dateFormat.toLocalizedPattern()).setRequired(true);

		if(allDay) {
			allDayButton.setModel(new StringResourceModel("AllDay_label_False", this, null));
		} else {
			allDayButton.setModel(new StringResourceModel("AllDay_label_True", this, null));
		}

		EventForm.this.addOrReplace(startDateTextField);
		EventForm.this.addOrReplace(endDateTextField);
	}

	// Getters and Setters for Model Support

	public Integer getInterval() {
		return interval;
	}

	public void setInterval(Integer interval) {
		this.interval = interval;
	}

	public Integer getCount() {
		return count;
	}

	public void setCount(Integer count) {
		this.count = count;
	}

	public Date getUntil() {
		return until;
	}

	public void setUntil(Date until) {
		this.until = until;
	}

	public RecurrenceFrequentyStringResourceModel getFrequency(){
		return frequency;
	}
	public void setFrequency(RecurrenceFrequentyStringResourceModel frequency){
		this.frequency = frequency;
	}

}
